﻿global using Devonline.Core;
global using Devonline.Entity;
using System.Linq.Dynamic.Core;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Devonline.Caching;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using SixLabors.ImageSharp;

namespace Devonline.AspNetCore;

public static class AspNetCoreExtensions
{
    #region application builder extensions
    /// <summary>
    /// 使用 TSetting 类型获取配置文件 Application Setting 节点配置项, 并以单例形式添加到服务容器并返回
    /// </summary>
    /// <typeparam name="TSetting">Application Setting 配置项的类型名称</typeparam>
    /// <param name="builder">WebApplicationBuilder 实例</param>
    /// <param name="section">自定义设置节点名称, 默认配置节点: AppSetting</param>
    /// <param name="setup">在注册到依赖注入容器之前的处理方法</param>
    /// <returns></returns>
    public static TSetting AddSetting<TSetting>(this WebApplicationBuilder builder, string? section = default, Action<TSetting>? setup = default) where TSetting : class, new()
    {
        var setting = (string.IsNullOrWhiteSpace(section) || section == nameof(AppSetting)) ? builder.Configuration.GetSetting<TSetting>() : builder.Configuration.GetSetting<TSetting>(section) ?? new TSetting();
        setup?.Invoke(setting);

        if (setting is HttpSetting httpSetting)
        {
            httpSetting.IdentityDbContext ??= builder.Configuration.GetConnectionString(nameof(httpSetting.IdentityDbContext));
        }

        builder.Services.AddSetting(setting);
        return setting;
    }
    #endregion

    #region service extensions
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <typeparam name="TSetting">Application Setting 配置项的类型名称</typeparam>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="setting">Application Setting 配置项的实例</param>
    /// <returns></returns>
    public static IServiceCollection AddSetting<TSetting>(this IServiceCollection services, TSetting setting) where TSetting : class, new()
    {
        services.AddSingleton(setting);
        if (typeof(TSetting) != typeof(AppSetting) && setting is AppSetting appSetting)
        {
            services.AddSingleton(appSetting);
            if (appSetting is HttpSetting httpSetting)
            {
                services.AddSingleton(httpSetting);
            }
        }

        return services;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddDataService(this IServiceCollection services)
    {
        services.AddScoped<IFileService, FileService>();
        services.AddScoped(typeof(IDataService<,>), typeof(DataService<,>));
        services.AddScoped(typeof(IAttachmentService<>), typeof(AttachmentService<>));
        services.AddScoped(typeof(IDataWithAttachmentService<,>), typeof(DataWithAttachmentService<,>));
        services.AddScoped(typeof(IDataWithCollectionService<,,>), typeof(DataWithCollectionService<,,>));
        services.AddScoped(typeof(IDataWithCollectionAndAttachmentService<,,>), typeof(DataWithCollectionAndAttachmentService<,,>));
        return services;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的 Excel 导入导出操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddExcelService(this IServiceCollection services)
    {
        services.AddTransient<IExcelExportService, ExcelExportService>();
        services.AddTransient<IExcelImportService, ExcelImportService>();
        return services;
    }

    /// <summary>
    /// 注册默认 MVC 控制器
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IMvcBuilder AddControllers(this IServiceCollection services, HttpSetting httpSetting) => services.AddControllers(options => options.Filters.Add<ExceptionFilter>()).AddJsonOptions(httpSetting.DateTimeToString);
    /// <summary>
    /// 注册默认 MVC 控制器及页面
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IMvcBuilder AddControllersWithViews(this IServiceCollection services, HttpSetting httpSetting) => services.AddControllersWithViews(options => options.Filters.Add<ExceptionFilter>()).AddJsonOptions(httpSetting.DateTimeToString);

    /// <summary>
    /// 注册安全策略
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    public static IServiceCollection AddDefaultSecurityPolicy(this IServiceCollection services, HttpSetting httpSetting)
    {
        //启用跨域策略
        if (!string.IsNullOrWhiteSpace(httpSetting.CorsOrigins))
        {
            services.AddCors(options => options.AddPolicy(AppSettings.DEFAULT_CORS_POLICY, policy => policy.WithOrigins(httpSetting.CorsOrigins.Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).AllowAnyHeader().AllowAnyMethod()));
        }

        //启用重定向和 Cookie 策略
        if (httpSetting.ProtocolType == ProtocolType.Https && httpSetting.HttpFordwarded)
        {
            services.AddCookiePolicy(options =>
            {
                options.MinimumSameSitePolicy = httpSetting.SameSiteMode;
                options.OnAppendCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions);
                options.OnDeleteCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions);
            });
        }

        return services;
    }
    /// <summary>
    /// 注册默认的认证服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="identitySetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthentication(this IServiceCollection services, IdentitySetting identitySetting)
    {
        System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
        {
            ArgumentNullException.ThrowIfNull(identitySetting.ResponseType);
            ArgumentNullException.ThrowIfNull(identitySetting.Scope);
            options.Authority = identitySetting.Authority;
            options.ClientId = identitySetting.ClientId;
            options.ClientSecret = identitySetting.ClientSecret;
            options.ResponseType = identitySetting.ResponseType;
            options.ResponseMode = OpenIdConnectResponseMode.FormPost;
            options.Scope.AddRange(identitySetting.Scope.Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
            options.SaveTokens = identitySetting.SaveTokens;
            options.RequireHttpsMetadata = identitySetting.RequireHttpsMetadata;
            options.GetClaimsFromUserInfoEndpoint = identitySetting.GetClaimsFromUserInfoEndpoint;
            //options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            //options.SignedOutRedirectUri = "/Home/Index";

            //options.CorrelationCookie.Expiration = TimeSpan.FromMinutes(5);
            //options.NonceCookie.Expiration = TimeSpan.FromMinutes(5);
            //if (appSetting.ProtocolType == ProtocolType.Https)
            //{
            //    options.CorrelationCookie.SameSite = SameSiteMode.Lax;
            //    options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
            //    options.NonceCookie.SameSite = SameSiteMode.Lax;
            //    options.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
            //}
            //else
            //{
            //    options.CorrelationCookie.HttpOnly = true;
            //    options.NonceCookie.HttpOnly = true;
            //    options.RequireHttpsMetadata = false;
            //}
        });

        return services;
    }
    /// <summary>
    /// 使用 JwtBearer 方式认证服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="identitySetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddJwtBearerAuthentication(this IServiceCollection services, IdentitySetting identitySetting)
    {
        ArgumentNullException.ThrowIfNull(identitySetting);
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Authority = identitySetting.Authority;
            //options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateAudience = false };
            options.Audience = identitySetting.Scope;
            options.RequireHttpsMetadata = identitySetting.RequireHttpsMetadata;
        });

        return services;
    }
    /// <summary>
    /// 注册默认的授权服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="identitySetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthorization(this IServiceCollection services, IdentitySetting identitySetting)
    {
        ArgumentNullException.ThrowIfNull(identitySetting.Scope);
        services.AddAuthorizationBuilder().AddPolicy(AppSettings.DEFAULT_AUTHORIZATION_POLICY, policy =>
        {
            policy.RequireAuthenticatedUser();
            policy.RequireClaim(nameof(identitySetting.Scope).ToLowerInvariant(), identitySetting.Scope.Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
        });

        return services;
    }

    /// <summary>
    /// 注册通用服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="appSetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultServices(this IServiceCollection services, AppSetting appSetting)
    {
        services.AddLogging();
        services.AddHttpContextAccessor();
        services.AddResponseCompression();
        services.AddDataService();
        services.AddExcelService();

        if (appSetting.Cache is not null)
        {
            services.AddDistributedCache(appSetting.Cache);
        }

        if (appSetting is HttpSetting httpSetting)
        {
            if (httpSetting.HttpLogging is not null)
            {
                services.AddHttpLogging(options => options = httpSetting.HttpLogging);
            }

            if (httpSetting.Endpoint.Mode == EndpointMode.Distributed && (httpSetting.Endpoint.MachineCode != AppSettings.UNIT_ZERO || httpSetting.Endpoint.DataCenterCode != AppSettings.UNIT_ZERO))
            {
                //分布式配置
                KeyGenerator.Configure(options =>
                {
                    options.MachineCode = httpSetting.Endpoint.MachineCode;
                    options.DataCenterCode = httpSetting.Endpoint.DataCenterCode;
                });
            }

            if (httpSetting.EnableAuth)
            {
                services.AddAuthorization();
                if (httpSetting.Identity is not null)
                {
                    if (httpSetting.AuthType == AuthType.Oidc)
                    {
                        services.AddDefaultAuthentication(httpSetting.Identity);
                    }
                    else if (httpSetting.AuthType == AuthType.BearerToken)
                    {
                        services.AddJwtBearerAuthentication(httpSetting.Identity);
                    }
                    else
                    {
                        //不认证
                        services.AddAuthentication();
                    }
                }
            }

            if (httpSetting.ResourcesPath is not null)
            {
                services.AddLocalization(options => options.ResourcesPath = httpSetting.ResourcesPath);
            }

#if !DEBUG
            services.AddDefaultSecurityPolicy(httpSetting);
#endif
        }

        services.Configure<ApiBehaviorOptions>(config => config.SuppressModelStateInvalidFilter = true);
        return services;
    }

    /// <summary>
    /// 添加 Http 日志
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="loggingSetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddHttpLogging(this IServiceCollection services, HttpLoggingSetting? loggingSetting = default)
    {
        if (loggingSetting is not null)
        {
            services.AddHttpLogging(options => options = loggingSetting);
        }

        return services;
    }
    #endregion

    #region mvc & api extensions
    /// <summary>
    /// 用于依赖注入的 json 全局设置
    /// 为避免客户端字段名首字母大小写的问题, 将不再使用 CamelCase
    /// </summary>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder, bool datetimeToString = false)
    {
        builder.AddJsonOptions(option =>
        {
            option.JsonSerializerOptions.AllowTrailingCommas = true;
            option.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
            option.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
            option.JsonSerializerOptions.IgnoreReadOnlyFields = false;
            option.JsonSerializerOptions.IgnoreReadOnlyProperties = false;
            option.JsonSerializerOptions.IncludeFields = false;
            option.JsonSerializerOptions.MaxDepth = AppSettings.UNIT_FOUR;
            option.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals;
            option.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
            option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            option.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
            option.JsonSerializerOptions.WriteIndented = true;

            //枚举使用字符串表示形式
            option.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));

            if (datetimeToString)
            {
                //自定义时间格式
                option.JsonSerializerOptions.Converters.Add(new DateTimeConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
                option.JsonSerializerOptions.Converters.Add(new DateTimeOffsetConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
            }
        });

        return builder.AddNewtonsoftJson(option =>
        {
            //使用驼峰样式的key
            option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

            //忽略循环引用
            option.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            option.SerializerSettings.TypeNameHandling = TypeNameHandling.None;
            option.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

            //设置时间格式
            option.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
            option.SerializerSettings.DateParseHandling = DateParseHandling.DateTime;
            option.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;

            if (datetimeToString)
            {
                //自定义时间格式
                option.SerializerSettings.DateFormatString = AppSettings.DEFAULT_DATETIME_FORMAT;
            }

            //枚举类型转字符串输出
            option.SerializerSettings.Converters.Add(new StringEnumConverter());
        });
    }
    #endregion

    #region IApplicationBuilder extensions
    /// <summary>
    /// 启动安全策略
    /// </summary>
    /// <param name="app">IApplicationBuilder</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IApplicationBuilder UseDefaultSecurityPolicy(this IApplicationBuilder app, HttpSetting httpSetting)
    {
        //启用跨域策略
        if (!string.IsNullOrWhiteSpace(httpSetting.CorsOrigins))
        {
            app.UseCors(AppSettings.DEFAULT_CORS_POLICY);
        }

        //启动重定向和 Cookie 策略
        if (httpSetting.ProtocolType == ProtocolType.Https && httpSetting.HttpFordwarded)
        {
            //使用 webserver header 配置进行 http 重定向
            var fordwardedHeaderOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto };
            fordwardedHeaderOptions.KnownNetworks.Clear();
            fordwardedHeaderOptions.KnownProxies.Clear();
            app.UseForwardedHeaders(fordwardedHeaderOptions);

            app.UseCookiePolicy(new CookiePolicyOptions
            {
                MinimumSameSitePolicy = httpSetting.SameSiteMode,
                OnAppendCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions),
                OnDeleteCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions)
            });
        }

        return app;
    }
    /// <summary>
    /// 全局默认异常处理方法
    /// 在 WebApplication 下会自动记录异常日志
    /// </summary>
    /// <param name="app">IApplicationBuilder</param>
    /// <param name="action">Exception Handler</param>
    /// <returns></returns>
    public static IApplicationBuilder UseException(this IApplicationBuilder app, Action<HttpContext, Exception?>? action = default) => app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        ExceptionHandler = async context =>
        {
            var message = "服务器内部错误";
            var logger = (app as WebApplication)?.Logger;
            var exception = context.Features.Get<IExceptionHandlerFeature>();
            if (exception != null && exception.Error != null)
            {
                var errorMessage = exception.Error.GetMessage();
                switch (exception.Error)
                {
                    case BadHttpRequestException:
                        //业务类型的错误需要抛出 BadHttpRequestException 异常, 一律按 BadRequest(400) 处理
                        context.Response.StatusCode = StatusCodes.Status400BadRequest;
                        message = exception.Error.Message;
                        logger?.LogInformation(message);
                        break;
                    case UnauthorizedAccessException:
                        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                        message = "用户尚未登陆, 请先登录!";
                        break;
                    case DbUpdateException:
                        //数据存在依赖不可删除时
                        context.Response.StatusCode = StatusCodes.Status400BadRequest;
                        message = "当前数据正在使用中, 不能删除, 请先删除正在使用此数据的依赖数据, 在尝试删除当前数据!";
                        logger?.LogWarning(message);
                        break;
                    default:
                        logger?.LogError(exception.Error, exception.Error.GetMessage());
                        break;
                }
#if !DEBUG
                message = errorMessage;
#endif
            }

            //context.Response.ContentType = ContentType.Json;
            action?.Invoke(context, exception?.Error);
            await context.Response.WriteAsync(message).ConfigureAwait(false);
        }
    });
    /// <summary>
    /// 启动默认 IApplicationBuilder
    /// </summary>
    /// <param name="app">WebApplication</param>
    /// <param name="httpSetting">配置项</param>
    /// <param name="beforeAuth">在启用认证与授权之前执行的方法委托</param>
    /// <returns></returns>
    public static IApplicationBuilder UseDefaultBuilder(this WebApplication app, HttpSetting httpSetting, Action? beforeAuth = default)
    {
        // Configure the HTTP request pipeline.
        if (app.Environment.IsProduction())
        {
            app.UseExceptionHandler("/Error");
        }
        else
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseException();

        if (httpSetting.UseStaticFiles)
        {
            app.UseStaticFiles();
        }

        if (httpSetting.HttpLogging is not null)
        {
            app.UseHttpLogging();
        }

        app.UseRouting();

#if !DEBUG
        app.UseDefaultSecurityPolicy(httpSetting);
#endif

        beforeAuth?.Invoke();

        app.UseResponseCompression();

        if (httpSetting.EnableAuth)
        {
            app.UseAuthentication();
            app.UseAuthorization();
            app.MapControllers().RequireAuthorization();
            app.MapDefaultControllerRoute().RequireAuthorization();
        }
        else
        {
            app.MapControllers();
            app.MapDefaultControllerRoute();
        }

        app.MapControllerRoute(name: "api", pattern: "api/{controller=Home}/{action=Index}/{id?}");

        return app;
    }
    #endregion

    #region http request extensions
    /// <summary>
    /// 获取查询选项, 将来自 HttpPost 的 OData 查询选项加入到 QueryString 键值对中
    /// </summary>
    /// <param name="request">WebApi 的 HttpRequest 对象</param>
    /// <returns>返回合并后的查询选项</returns>
    public static IEnumerable<KeyValuePair<string, StringValues>> GetRequestOptions(this HttpRequest request)
    {
        var queryString = request.Query.ToList();
        if (request.HasFormContentType && (request.Form.Keys?.Count ?? 0) > 0)
        {
            queryString.AddRange(request.Form);
        }

        if (request.Headers.IsNotNullOrEmpty())
        {
            queryString.AddRange(request.Headers);
        }

        if (request.Cookies.IsNotNullOrEmpty())
        {
            queryString.AddRange(request.Cookies.ToDictionary(x => x.Key, x => new StringValues(x.Value)));
        }

        return queryString;
    }
    /// <summary>
    /// 从当前 request 中获取参数的值, 取值顺序 Header -> Query -> Form -> Cookie
    /// 其中一旦 Header 中有值, 会无视/覆盖其他集合中已经读取的值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="request"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetRequestOption<T>(this HttpRequest request, string key) where T : IConvertible
    {
        var value = string.Empty;
        if (request.Query.Keys.Contains(key))
        {
            value = request.Query[key];
        }
        else if (request.HasFormContentType && request.Form.Keys.IsNotNullOrEmpty() && request.Form.Keys.Contains(key))
        {
            value = request.Form[key];
        }
        else if (request.Cookies.IsNotNullOrEmpty() && request.Cookies.Keys.Contains(key))
        {
            value = request.Cookies[key];
        }

        //因为只有 Header 的值从服务器端可以设置, 因此优先级最高, 用于防止客户端故意破坏
        if (request.Headers.IsNotNullOrEmpty() && request.Headers.TryGetValue(key, out StringValues headerValue))
        {
            value = headerValue;
        }

        if (!string.IsNullOrWhiteSpace(value))
        {
            return value.To<T>();
        }

        return default;
    }
    /// <summary>
    /// 以最高优先级设置 Request 中的查询选项
    /// </summary>
    /// <param name="request"></param>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetRequestOption(this HttpRequest request, string key, string value) => request.Headers[key] = value;
    /// <summary>
    /// 限制请求的分页中每页查询结果返回的行数
    /// </summary>
    /// <param name="request"></param>
    /// <param name="pageSize"></param>
    public static void LimitRequestPageSize(this HttpRequest request, int pageSize = AppSettings.DEFAULT_PAGE_SIZE)
    {
        var value = pageSize.ToString();
        request.SetRequestOption(AppSettings.QUERY_OPTION_TOP, value);
        request.SetRequestOption(nameof(PagedRequest.PageSize).FirstCharToLower(), value);
    }
    /// <summary>
    /// 获取请求的 origin 字符串
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static string GetRequestOrigin(this HttpRequest request) => new(new StringBuilder()
         .Append(request.Scheme)
         .Append(AppSettings.DEFAULT_PROTOCOL_SPLITER)
         .Append(request.Host)
         .ToString());
    /// <summary>
    /// 获取请求的 path 字符串, 不带请求参数的部分
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static string GetRequestPath(this HttpRequest request)
    {
        var builder = new StringBuilder()
             .Append(request.Scheme)
             .Append(AppSettings.DEFAULT_PROTOCOL_SPLITER)
             .Append(request.Host)
             .Append(request.PathBase);
        if (request.Path.HasValue && request.Path != AppSettings.CHAR_SLASH.ToString())
        {
            builder.Append(request.Path);
        }

        return new(builder.ToString());
    }
    /// <summary>
    /// 获取请求的完整 uri 字符串
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static string GetRequestUri(this HttpRequest request)
    {
        var builder = new StringBuilder()
             .Append(request.Scheme)
             .Append(AppSettings.DEFAULT_PROTOCOL_SPLITER)
             .Append(request.Host)
             .Append(request.PathBase);
        if (request.Path.HasValue && request.Path != AppSettings.CHAR_SLASH.ToString())
        {
            builder.Append(request.Path).Append(request.QueryString);
        }

        return new(builder.ToString());
    }
    /// <summary>
    /// 获取请求的 IP 地址
    /// </summary>
    /// <param name="request"></param
    /// <param name="header"></param>
    /// <returns></returns>
    public static string GetRequestIpAddress(this HttpRequest request, string? header = default) => request.Headers[header ?? AppSettings.HTTP_HEADER_FORWARDED_FOR].FirstOrDefault()
            ?? request.Headers[AppSettings.HTTP_HEADER_REAL_IP].FirstOrDefault()
            ?? request.HttpContext.Connection.RemoteIpAddress?.ToString()
            ?? string.Empty;
    /// <summary>
    /// 获取请求的 IPv4 地址
    /// </summary>
    /// <param name="request"></param
    /// <param name="header"></param>
    /// <returns></returns>
    public static string GetRequestIpv4Address(this HttpRequest request, string? header = default) => request.Headers[header ?? AppSettings.HTTP_HEADER_FORWARDED_FOR].FirstOrDefault()
            ?? request.Headers[AppSettings.HTTP_HEADER_REAL_IP].FirstOrDefault()
            ?? request.HttpContext.Connection.RemoteIpAddress?.MapToIPv4().ToString()
            ?? string.Empty;
    /// <summary>
    /// 获取请求中的 columns 字段列表并转换为字符串数组
    /// </summary>
    /// <typeparam name="T">字段存在的校验类型</typeparam>
    /// <param name="request">Http 请求</param
    /// <param name="useCamelCase">是否使用驼峰法</param>
    /// <returns></returns>
    public static IEnumerable<string>? GetRequestColumns<T>(this HttpRequest request, bool useCamelCase = true)
    {
        string? columns = request.GetRequestOption<string>(nameof(columns));
        if (columns is not null)
        {
            var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            var columnNames = columns.Split(AppSettings.CHAR_COMMA, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
            if (columnNames is not null && columnNames.Length > 0)
            {
                if (useCamelCase)
                {
                    columnNames = columnNames.Select(x => x.FirstCharToUpper()).ToArray();
                }

                var notExists = columnNames.Where(x => !propertyInfos.Any(a => a.PropertyType.GetCoreType().IsSampleType() && a.Name == x));
                if (notExists.Any())
                {
                    throw new HttpRequestException($"请求参数 columns 中, 列 {notExists.ToString<string>()} 不存在!");
                }

                return columnNames;
            }
        }

        return null;
    }
    /// <summary>
    /// 通过 request query 获取 QueryOptions 的实例
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="request"></param>
    /// <returns></returns>
    public static QueryOptions<T> GetQueryOptions<T>(this HttpRequest request) => new(new QueryOptionRawValue(request.Query));
    /// <summary>
    /// http request 基础分页请求参数
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static PagedRequest GetPagedRequest(this HttpRequest request)
    {
        var pagedRequest = new PagedRequest
        {
            Total = request.GetRequestOption<bool>(nameof(PagedRequest.Total).ToLowerInvariant()),
            PageIndex = request.GetRequestOption<int>(nameof(PagedRequest.PageIndex).FirstCharToLower()),
            PageSize = request.GetRequestOption<int>(nameof(PagedRequest.PageSize).FirstCharToLower()),
            Orderby = request.GetRequestOption<string>(nameof(PagedRequest.Orderby).FirstCharToLower())
        };

        if (pagedRequest.PageIndex <= AppSettings.UNIT_ONE)
        {
            pagedRequest.PageIndex = request.GetRequestOption<int>(nameof(PagedRequest.PageIndex).ToLowerInvariant());
            if (pagedRequest.PageIndex <= AppSettings.UNIT_ONE)
            {
                //增加 ant design 分页的 current 支持
                pagedRequest.PageIndex = request.GetRequestOption<int>(nameof(AntDesign.AntDesignData.Current).ToLowerInvariant());
                if (pagedRequest.PageIndex <= AppSettings.UNIT_ONE)
                {
                    pagedRequest.PageIndex = AppSettings.DEFAULT_PAGE_INDEX;
                }
            }
        }

        if (pagedRequest.PageSize <= AppSettings.UNIT_ONE)
        {
            pagedRequest.PageSize = request.GetRequestOption<int>(nameof(PagedRequest.PageSize).ToLowerInvariant());
            if (pagedRequest.PageSize <= AppSettings.UNIT_ONE)
            {
                pagedRequest.PageSize = AppSettings.DEFAULT_PAGE_SIZE;
            }
        }

        return pagedRequest;
    }
    /// <summary>
    /// 根据上下文和请求中的分页条件获取分页的查询结果
    /// </summary>
    /// <typeparam name="T">操作的数据类型</typeparam>
    /// <param name="request">Http 请求</param>
    /// <param name="queryable">针对数据的查询表达式</param>
    /// <returns></returns>
    public static async Task<PagedResult> GetPagedResultAsync<T>(this HttpRequest request, IQueryable<T> queryable) where T : class
    {
        var queryOptions = new QueryOptions<T>(new QueryOptionRawValue(request.Query));
        if (queryOptions.Filter is not null)
        {
            if (queryOptions.Filter is NestedFilterOption<T> filter && filter.Filters is not null && filter.Filters.Any())
            {
                //目前仅支持一级嵌套, 且顶级条件仅支持 and
                //var expression = string.Join(GetFilterLogic(filter.Logic ?? LogicType.And), filter.Filters.Select(x => GetFilterExpression(x)));
                queryable = queryable.Where(string.Join(filter.GetFilterLogic(), filter.Filters.Select(x => x.GetFilterExpression())));
            }
            else if ((!string.IsNullOrWhiteSpace(queryOptions.Filter.Field)) && (!string.IsNullOrWhiteSpace(queryOptions.Filter.Value)))
            {
                queryable = queryable.Where(queryOptions.Filter.GetFilterExpression(), queryOptions.Filter.Value);
            }
        }

        if (queryOptions.Orderby is not null)
        {
            queryable = queryable.OrderBy(queryOptions.Orderby.ToString());
        }

        //如果 count 查询表达式不传值, 则不分页
        var result = new PagedResult();
        var pagedRequest = request.GetPagedRequest();
        if (queryOptions.Count.HasValue && queryOptions.Count.Value)
        {
            pagedRequest = queryOptions;
        }
        else if (!queryOptions.Count.HasValue && pagedRequest.Total.HasValue && pagedRequest.Total.Value)
        {
            PagedOptions pagedOptions = pagedRequest;
            queryOptions.Count = pagedOptions.Count;
            queryOptions.Skip = pagedOptions.Skip;
            queryOptions.Top = pagedOptions.Top;
        }

        if (queryOptions.Count.HasValue && queryOptions.Count.Value)
        {
            result.PageIndex = pagedRequest.PageIndex;
            result.PageSize = pagedRequest.PageSize;
            result.Total = await queryable.CountAsync();
            if (queryOptions.Skip.HasValue && queryOptions.Skip.Value > 0)
            {
                queryable = queryable.Skip(queryOptions.Skip.Value);
            }

            if (queryOptions.Top.HasValue && queryOptions.Top.Value > 0)
            {
                queryable = queryable.Take(queryOptions.Top.Value);
            }
        }

        if (queryOptions.Expand is not null)
        {
            var columns = queryOptions.Expand.ToString().Split(AppSettings.CHAR_COMMA, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
            if (columns.Length > 0)
            {
                foreach (var column in columns)
                {
                    queryable = queryable.Include(column);
                }
            }
        }

        if (queryOptions.Select is not null)
        {
            var columns = queryOptions.Select.ToString<string>();
            result.Data = await queryable.Select("new {" + columns + "}").ToDynamicListAsync();
        }
        else
        {
            result.Data = await queryable.ToListAsync();
        }

        return result;
    }
    #endregion

    #region http context extensions
    /// <summary>
    /// 从 claims 中获取第一个类型为 type 的值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="claims"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    public static string? GetClaimValue(this IEnumerable<Claim> claims, string type, string? provider = default)
    {
        string? value = null;
        if (!string.IsNullOrWhiteSpace(provider))
        {
            value = claims.FirstOrDefault(x => x.Type.EndsWith((provider + AppSettings.CHAR_COLON + type).ToLowerInvariant()))?.Value;
        }

        value ??= claims.FirstOrDefault(x => x.Type == type)?.Value;
        return value;
    }
    /// <summary>
    /// 获取上下文对象中指定 Claim type 的值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    public static T? GetClaimValue<T>(this HttpContext httpContext, string type) where T : IConvertible
    {
        var value = httpContext.User.Claims.FirstOrDefault(c => c.Type == type)?.Value;
        return !string.IsNullOrWhiteSpace(value) ? value.To<T>() : default;
    }
    /// <summary>
    /// 获取上下文对象中参数/变量的值, 先取 http context 的 Claims, 后取 request
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetContextOption<T>(this HttpContext httpContext, string key) where T : IConvertible => httpContext.GetClaimValue<T>(key) ?? httpContext.Request.GetRequestOption<T>(key);
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识取值顺序为: 在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// User.Claims 中的 type 为 userId 的值 -> User.Claims 中的 type 为 id 的值 -> User.Claims 中的 type 为 sub 的值
    /// 不在主动获取 request 中 userId 的参数值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static T? GetUserId<T>(this HttpContext httpContext) where T : IConvertible => httpContext.GetClaimValue<T>(AppSettings.CLAIM_TYPE_USER_ID)
            ?? httpContext.GetClaimValue<T>(AppSettings.CLAIM_TYPE_JWT_SUBJECT)
            ?? httpContext.GetClaimValue<T>(AppSettings.CLAIM_TYPE_ID)
            ?? httpContext.GetClaimValue<T>(ClaimTypes.PrimarySid)
            ?? httpContext.GetClaimValue<T>(ClaimTypes.Sid);
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识取值顺序为: 在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// User.Claims 中的 type 为 userId 的值 -> User.Claims 中的 type 为 id 的值 -> User.Claims 中的 type 为 sub 的值
    /// 不在主动获取 request 中 userId 的参数值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static T? GetGroupId<T>(this HttpContext httpContext) where T : IConvertible => httpContext.GetClaimValue<T>(AppSettings.CLAIM_TYPE_GROUP_ID)
            ?? httpContext.GetClaimValue<T>(ClaimTypes.PrimaryGroupSid)
            ?? httpContext.GetClaimValue<T>(ClaimTypes.GroupSid);
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识取值顺序为: 在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// User.Claims 中的 type 为 userId 的值 -> User.Claims 中的 type 为 id 的值 -> User.Claims 中的 type 为 sub 的值
    /// 不在主动获取 request 中 userId 的参数值
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static string? GetUserId(this HttpContext httpContext) => httpContext.GetUserId<string>();
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识取值顺序为: 在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// User.Claims 中的 type 为 userId 的值 -> User.Claims 中的 type 为 id 的值 -> User.Claims 中的 type 为 sub 的值
    /// 不在主动获取 request 中 userId 的参数值
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static string? GetGroupId(this HttpContext httpContext) => httpContext.GetGroupId<string>();
    /// <summary>
    /// 从 httpContext 获取用户名, 用户名取值顺序为:
    /// User.Claims 中的 type 为 userName 的值 -> User.Identity.Name -> User.Claims 中的 type 为 name, Name 的值, 无值则取 anonymous
    /// 不在主动获取 request 中 userName 的参数值
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static string GetUserName(this HttpContext httpContext) => httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_USER_NAME)
            ?? httpContext.User?.Identity?.Name
            ?? httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_NAME)
            ?? httpContext.GetClaimValue<string>(ClaimTypes.Name)
            ?? httpContext.GetClaimValue<string>(ClaimTypes.NameIdentifier)
            ?? AppSettings.USER_ANONYMOUS;

    /// <summary>
    /// 获取请求的 IP 地址
    /// </summary>
    /// <param name="httpContext"></param
    /// <param name="header"></param>
    /// <returns></returns>
    public static string GetRequestIpAddress(this HttpContext httpContext, string? header = default) => httpContext.Request.Headers[header ?? AppSettings.HTTP_HEADER_FORWARDED_FOR].FirstOrDefault()
            ?? httpContext.Request.Headers[AppSettings.HTTP_HEADER_REAL_IP].FirstOrDefault()
            ?? httpContext.Connection.RemoteIpAddress?.ToString()
            ?? string.Empty;
    /// <summary>
    /// 获取请求的 IPv4 地址
    /// </summary>
    /// <param name="httpContext"></param
    /// <param name="header"></param>
    /// <returns></returns>
    public static string GetRequestIpv4Address(this HttpContext httpContext, string? header = default) => httpContext.Request.Headers[header ?? AppSettings.HTTP_HEADER_FORWARDED_FOR].FirstOrDefault()
            ?? httpContext.Request.Headers[AppSettings.HTTP_HEADER_REAL_IP].FirstOrDefault()
            ?? httpContext.Connection.RemoteIpAddress?.MapToIPv4().ToString()
            ?? string.Empty;
    #endregion

    #region 其他扩展
    /// <summary>
    /// 枚举按顺序生成基础数据
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="index">当前枚举的分配编号</param>
    /// <param name="toLower">key 的值是否使用整体小写</param>
    /// <returns></returns>
    public static List<Parameter> GetParametersFromEnum<TEnum>(this int index, bool toLower = false) where TEnum : struct, Enum
    {
        var type = typeof(TEnum);
        var parameter = new Parameter
        {
            Id = index.ToString(),
            Index = index,
            ParentId = "0",
            Key = type.Name.FirstCharToLower(),
            Value = type.Name,
            Text = type.GetDisplayName()
        };

        index = 0;
        parameter.Create();
        var list = new List<Parameter> { parameter };
        var values = Enum.GetValues<TEnum>();
        foreach (var @enum in values)
        {
            var enumValue = Convert.ToInt32(@enum);
            var id = parameter.Index * AppSettings.UNIT_THOUSAND + enumValue;
            if (enumValue == index++)
            {
                id++;
            }

            var value = @enum.ToString();
            var child = new Parameter
            {
                Id = id.ToString(),
                Index = id,
                ParentId = parameter.Id,
                Key = toLower ? value.ToLowerInvariant() : value.FirstCharToLower(),
                Value = value,
                Text = @enum.GetDisplayName()!
            };

            child.Create();
            list.Add(child);
        }

        return list;
    }
    /// <summary>
    /// bool 型枚举按顺序生成基础数据
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="index">当前枚举的分配编号</param>
    /// <param name="toLower">key 的值是否使用整体小写</param>
    /// <returns></returns>
    public static List<Parameter> GetBoolParametersFromEnum<TEnum>(this int index, bool toLower = false) where TEnum : struct, Enum
    {
        var type = typeof(TEnum);
        var parameter = new Parameter
        {
            Id = index.ToString(),
            Index = index,
            ParentId = "0",
            Key = type.Name.FirstCharToLower(),
            Value = type.Name,
            Text = type.GetDisplayName()
        };

        index = 0;
        parameter.Create();
        var list = new List<Parameter> { parameter };
        var values = Enum.GetValues<TEnum>();
        if (values.Length != 2 && values.Length != 3)
        {
            throw new Exception("作为 bool 值的枚举项只能有两个或三个!");
        }

        foreach (var @enum in values)
        {
            var enumValue = Convert.ToInt32(@enum);
            var value = @enum.ToString();
            var id = parameter.Index * AppSettings.UNIT_THOUSAND + enumValue;
            if (enumValue == index++)
            {
                id++;
            }

            var child = new Parameter
            {
                Id = id.ToString(),
                Index = id,
                ParentId = parameter.Id,
                Key = toLower ? value.ToLowerInvariant() : value.FirstCharToLower(),
                Text = @enum.GetDisplayName()!
            };

            if (enumValue == 0)
            {
                child.Value = false.ToString().ToLower();
            }
            else if (enumValue == 1)
            {
                child.Value = true.ToString().ToLower();
            }
            else
            {
                child.Value = "null";
            }

            child.Create();
            list.Add(child);
        }

        return list;
    }

    /// <summary>
    /// 根据总数目, 页大小, 获取总页数
    /// </summary>
    /// <param name="total">总数目</param>
    /// <param name="size">页大小</param>
    /// <returns></returns>
    public static long GetPageCount(this long total, int size) => ((total - 1) / size) + 1;
    /// <summary>
    /// 获取当前进度的百分比
    /// </summary>
    /// <param name="total">总数目</param>
    /// <param name="index">当前数目</param>
    /// <returns></returns>
    public static double GetPercent(this long total, int index) => Math.Round(index * 100 / (float)total);
    #endregion

    #region 内部方法
    /// <summary>
    /// 设置 Cookie 策略
    /// </summary>
    /// <param name="httpContext"></param>
    /// <param name="options"></param>
    private static void SetSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None && DisallowsSameSiteNone(httpContext.Request.Headers.UserAgent.ToString()))
        {
            options.SameSite = SameSiteMode.Lax;
        }
    }
    /// <summary>
    /// Checks if the UserAgent is known to interpret an unknown value as Strict.
    /// For those the <see cref="CookieOptions.SameSite" /> property should be
    /// set to <see cref="Unspecified" />.
    /// </summary>
    /// <remarks>
    /// This code is taken from Microsoft:
    /// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
    /// </remarks>
    /// <param name="userAgent">The user agent string to check.</param>
    /// <returns>Whether the specified user agent (browser) accepts SameSite=None or not.</returns>
    private static bool DisallowsSameSiteNone(string userAgent)
    {
        // Cover all iOS based browsers here. This includes:
        //   - Safari on iOS 12 for iPhone, iPod Touch, iPad
        //   - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
        //   - Chrome on iOS 12 for iPhone, iPod Touch, iPad
        // All of which are broken by SameSite=None, because they use the
        // iOS networking stack.
        // Notes from Thinktecture:
        // Regarding https://caniuse.com/#search=samesite iOS versions lower
        // than 12 are not supporting SameSite at all. Starting with version 13
        // unknown values are NOT treated as strict anymore. Therefore we only
        // need to check version 12.
        if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
        {
            return true;
        }

        // Cover Mac OS X based browsers that use the Mac OS networking stack.
        // This includes:
        //   - Safari on Mac OS X.
        // This does not include:
        //   - Chrome on Mac OS X
        // because they do not use the Mac OS networking stack.
        // Notes from Thinktecture:
        // Regarding https://caniuse.com/#search=samesite MacOS X versions lower
        // than 10.14 are not supporting SameSite at all. Starting with version
        // 10.15 unknown values are NOT treated as strict anymore. Therefore we
        // only need to check version 10.14.
        if (userAgent.Contains("Safari") && userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && userAgent.Contains("Version/"))
        {
            return true;
        }

        // Cover Chrome 50-69, because some versions are broken by SameSite=None
        // and none in this range require it.
        // Note: this covers some pre-Chromium Edge versions,
        // but pre-Chromium Edge does not require SameSite=None.
        // Notes from Thinktecture:
        // We can not validate this assumption, but we trust Microsofts
        // evaluation. And overall not sending a SameSite value equals to the same
        // behavior as SameSite=None for these old versions anyways.
        if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
        {
            return true;
        }

        return false;
    }
    #endregion
}